

#include "httpclient.h"

HttpClient::HttpClient (const QString &host,
                        const quint16 port,
                        const QString &path,
                        const HttpMethod method ,
                        QObject* parent) :
    NetworkClient( host, port, parent ),
    httpState( Waiting ),
    method( method )
{
    #ifdef HTTP_CLIENT_DEBUG
    qDebug()<< this << "HttpClient(" << host << "," << ( method == GET ? "GET" : "POST" ) << ")";
    #endif
    url.setScheme( "http" );
    url.setHost( host );
    url.setPort( port );
    url.setPath( path );
    connect( this, SIGNAL(done()), SLOT(onProtocolDone()) );
}

HttpClient::~HttpClient()
{
}

void HttpClient::onReadyRead()
{
    switch ( httpState )
        {
        case Sending:
            #ifdef HTTP_CLIENT_DEBUG
            qDebug() << this << "onReadyRead(), Sending";
            #endif
            httpState = ReadingResponseHeader;
        case ReadingResponseHeader:
            #ifdef HTTP_CLIENT_DEBUG
            qDebug() << this << "onReadyRead(), ReadingResponseHeader";
            #endif

            /* если не дочитан */
            if ( !readResponseHeader() ) break;
            if ( responseHeader.statusCode() == 100 )
              {

                /*
                 * Continue ;
                 * это нам говорят продолжай слать пост, игнорируем, опять будем читать хидер
                 */
                break;
              }
            else if ( responseHeader.statusCode() == 302 )
              {

                /* Moved temporary */
                if ( responseHeader.hasKey( "Location") )
                  {
                    QString location= responseHeader.value( "Location" );
                    if ( location.at( 0) == '/' ) url.setPath( location );
                    else url.setUrl( location );
                    method= GET;
                  }
                break;
              }

            httpState = ReadingResponseBody;
        case ReadingResponseBody:
            #ifdef HTTP_CLIENT_DEBUG
            qDebug() << this << "onReadyRead(), ReadingResponseBody";
            #endif

            /* если не дочитан */
            if ( !readResponseBody() ) break;
            emitDone();
            break;
        default:
            qCritical() << this << "onReadyRead(): unknown httpState";
            qFatal( "programming error" );
        }
}

void HttpClient::protocolStop()
{
    NetworkClient::protocolStop();
    httpState = Waiting;
}

void HttpClient::protocolStart()
{
    NetworkClient::protocolStart();
    Q_ASSERT( httpState == Waiting );

    QString path= url.path();
    if ( path.isEmpty() ) path= "/";
    #if (QT_VERSION < QT_VERSION_CHECK(5, 0, 0))
    if ( method == GET && url.hasQuery() ) path+= "?" + url.encodedQuery();
    #else
    if ( method == GET && url.hasQuery() ) path+= "?" + url.query();
    #endif

    HttpRequestHeader  h( method == GET ? "GET" : "POST", path );
    h.setValue( "Host", QUrl::toAce( url.host()) );
    h.setValue( "User-Agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 2.0.50727; .NET CLR 3.0.04506.30; .NET CLR 3.0.04506.648)" );
    h.setValue( "Accept", "*/*" );
    if ( !referer.isEmpty() )
        h.setValue( "Referer", referer );

    QList<QNetworkCookie>   cookieList= cookieJar.cookiesForUrl( url );
    foreach( QNetworkCookie nc, cookieList )
      {
        QByteArray  cookieNameVal;
        cookieNameVal.append( nc.name() );
        cookieNameVal.append( "=" );
        cookieNameVal.append( nc.value() );
        h.addValue( "Cookie", cookieNameVal );
      }

    if ( method == POST )
      {
        h.setValue( "Content-Type", "text/xml" );
        h.setValue( "Content-Length", QString::number( postData.length()) );
      }

    QByteArray  requestHeader( h.toString().toLatin1() );
    if ( method == POST ) requestHeader.append( postData );
    #ifdef TRACE_HTTP
    qDebug() << "--- request header ---" << endl << requestHeader
             << endl <<"--- --------- ---";
    #endif
    sureWrite( requestHeader );
    httpState= Sending;
    #ifdef HTTP_CLIENT_DEBUG
    qDebug() << this << "HttpClient::protocolStart(), Clear response data";
    #endif
    responseHeaderData.clear();
    responseBodyData.clear();
}

bool HttpClient::readResponseHeader()
{
    #ifdef HTTP_CLIENT_DEBUG
    qDebug() << this << "HttpClient::readResponseHeader()";
    #endif

    bool        end= false;
    QByteArray  tmp;
    QByteArray rn( "\r\n", 2 ), n( "\n", 1 );
    while ( !end && socket->canReadLine() )
      {
        tmp= socket->readLine();
        if ( tmp == rn || tmp == n || tmp.isEmpty() ) end = true;
        else responseHeaderData+= tmp;
      }

    if ( !end ) return false;
    responseHeader = HttpResponseHeader( QString( responseHeaderData) );
    #ifdef TRACE_HTTP
    qDebug() << "--- response header ---" << endl << responseHeader.toString();
    #endif
    if ( responseHeader.isValid() )
      {
        if ( responseHeader.hasKey( "Set-Cookie") )
          {
            QByteArray                      cba;
            QList<QPair<QString, QString> > keys= responseHeader.values();
            QPair<QString, QString>         p;
            foreach( p, keys )
              {
                if ( p.first == "Set-Cookie" )
                  {
                    cba.append( p.second );
                    cba.append( "\n" );
                  }
              }

            QList<QNetworkCookie>   cookieList= QNetworkCookie::parseCookies( cba );
            cookieJar.setCookiesFromUrl( cookieList, url );
          }

        return true;
      }

    qWarning() << this << "readResponseHeader(): invalid responseHeader";
    emitError( "Invalid response header" );
    return false;
}

bool HttpClient::readResponseBody()
{
    if ( responseHeader.hasContentLength() )
        return readContentLength();
    else if ( responseHeader.value( "Connection") == "close" )
      {
        responseBodyData+= socket->readAll();
        return false;
      }
    else
      {

        /*
         * QByteArray data = socket->readAll();
         * qCritical() << this << "XmlRpcClient::readResponseBody():" << "unknown content
         * read method" << endl << responseHeader.toString() << endl;
         * emitError( "Unknown content read method" );
         * ;
         * Code sended by Kevin Tallakson(ktallakson@gmail.com)
         */
        QByteArray              data= socket->peek( socket->bytesAvailable() );
        static const QByteArray METHOD_RESPONSE_TERM_TAG( "</methodResponse>" );
        int                     termTagIndex= data.indexOf( METHOD_RESPONSE_TERM_TAG );
        bool                    foundEndOfResponse= termTagIndex > 0;
        if ( foundEndOfResponse )
          {
            int remainderIndex= termTagIndex + METHOD_RESPONSE_TERM_TAG.size();
            if ( data[remainderIndex] == '\r' ) ++remainderIndex;
            if ( data[remainderIndex] == '\n' ) ++remainderIndex;
            data.truncate( remainderIndex );
          }

        responseBodyData+= data;
        socket->read( data.size() );
        return foundEndOfResponse;
      }

    return false;
}

bool HttpClient::readContentLength()
{
    qint64      l= ( qint64 ) responseHeader.contentLength();
    qint64      n= qMin( socket->bytesAvailable(), l - responseBodyData.size() );
    QByteArray  readed( socket->read( n) );
    responseBodyData+= readed;
    #ifdef HTTP_CLIENT_DEBUG
    qDebug() << this << "readContentLength(), left:" << ( l - responseBodyData.length() );
    #endif
    return responseBodyData.length() == l;
}

void HttpClient::onProtocolDone()
{
    #ifdef HTTP_CLIENT_DEBUG
    qDebug() << this << "onProtocolDone()";
    #endif

    emit    dataRecieved();
    emit    dataReady( responseBodyData );
}

